Skip to content

feat: introduce Volar.js meta-language#2315

Open
auvred wants to merge 13 commits intomainfrom
volar-language
Open

feat: introduce Volar.js meta-language#2315
auvred wants to merge 13 commits intomainfrom
volar-language

Conversation

@auvred
Copy link
Member

@auvred auvred commented Feb 14, 2026

PR Checklist

Overview

This is a very complex PR and it's even hard to properly explain it in a linear order because things interact in weird ways here, so I'll try to explain it in several steps.

Brief overview:

We have two sets of global variables: in @flint.fyi/typescript-language (used by @flint.fyi/volar-language) and in @flint.fyi/volar-language (used by @flint.fyi/{astro,svelte,vue}-language). These variables are filled in reverse order by downstream packages when imported (@flint.fyi/vue-language -> @flint.fyi/volar-language -> @flint.fyi/typescript-language). Later they're used in normal order (@flint.fyi/typescript-language -> @flint.fyi/volar-language -> @flint.fyi/vue-language).

We have several concepts:

  • VolarCreateFile - callback which is registered in @flint.fyi/typescript-language from @flint.fyi/volar-language by calling setVolarCreateFile.

    Its purpose is to create a Flint's file for an extension language.

  • VolarLanguagePluginInitializer - contains Volar language plugins and VolarBasedLanguageCreateFile.

    Plugin initializers are registered globally when Volar-based Flint language is created.

    Every extension language (@flint.fyi/{astro,svelte,vue}-language) provides its own Volar language plugins and VolarBasedLanguageCreateFile.

  • VolarBasedLanguageCreateFile - function that returns extension language-specific info so that @flint.fyi/volar-language is able to create a Flint file.

  • Volar-based Flint language - meta language that is used to create actual extension language (e.g., @flint.fyi/vue-language calls createVolarBasedLanguage).
    This language is not used anywhere in Flint runtime.

    It has two purposes:

    • register VolarLanguagePluginInitializer globally
    • and provide type-safety for extension language rules

    These rules use typescriptLanguage as their language (more on this later).

  • Volar language - orchestrator of several Volar language plugins and Volar scripts. Both Svelte and Vue plugins can co-exist. Volar language decides which one should handle .svelte and .vue.

  • Volar language plugin - its responsibility is to generate "service script"

  • Volar's "source script" - wrapper around file's original source text

  • Volar's "service script" - wrapper around file's generated virtual text

  • Volar's "mapping" - maps regions of source text to the corresponding regions of generated virtual text

Now, the initialization order (I'll use Vue language for illustration, but this applies to Astro and Svelte as well):

  • User imports @flint.fyi/vue in flint.config.ts
  • @flint.fyi/vue contains rules that use @flint.fyi/vue-language language
  • @flint.fyi/vue-language registers .vue extension through @flint.fyi/ts-patch and creates a vueLanguage with createVolarBasedLanguage (it passes VolarLanguagePluginInitializer there)
  • createVolarBasedLanguage registers VolarLanguagePluginInitializer globally
  • @flint.fyi/volar-language sets global TS program creation proxy via @flint.fyi/ts-patch
  • @flint.fyi/volar-language calls setVolarCreateFile exported from @flint.fyi/typescript-language with VolarCreateFile callback
  • setVolarCreateFile saves this callback globally

At this point initialization is done (linting hasn't started yet!)

Now, @flint.fyi/core starts collecting files for linting:

  • Since Volar-based Flint language rules specify typescriptLanguage as their language, @flint.fyi/core calls typescriptLanguage.createFile({ filePath: 'myfile.vue' })

  • typescriptLanguage calls service.openClientFile('myfile.vue')

  • This creates TS program internally and @flint.fyi/volar-language intercepts program creation via @flint.fyi/ts-patch

  • It takes VolarLanguagePluginInitializers (including a @flint.fyi/vue-language's one) and evaluates them in order to get two things:

    • List of Volar language plugins
    • VolarBasedLanguageCreateFile - customizations provided by @flint.fyi/vue-language

    Also, it attaches VolarBasedLanguageCreateFile to Volar language plugin (__flintCreateFile), so that later we can access it.

  • Then, it attaches Volar language to the proxied TS program (__flintVolarLanguage)

  • At this moment proxied TS program is a fully functioning program that contains .vue files with generated virtual code

  • typescriptLanguage detects that .vue extension is not natively supported by TypeScript and hands off file creation to the VolarCreateFile callback. Since typescriptLanguage doesn't know anything about attached __flintVolarLanguage and __flintCreateFile properties, it just passes ts.Program and ts.SourceFile to the VolarCreateFile.

  • VolarCreateFile:

    • extracts __flintVolarLanguage from the ts.Program
    • extracts "source script", "service script" and "mappings" from Volar language by using ts.SourceFile's file name
    • extracts __flintCreateFile from the Volar language plugin attached to the "source script"
    • calls __flintCreateFile with ts.Program, ts.SourceFile, Volar language, "source script" and "service script"
  • __flintCreateFile (provided by @flint.fyi/vue-language) (PR - feat: introduce Vue language #2316):

    • parses source text with @vue/compiler-dom in order to get Vue AST
    • extracts Flint directives from comments in <template>
    • converts Vue parsing errors to Flint diagnostics
    • provides extraServices with Vue-specific extras, such as Vue AST (they're passed to rules later)
  • Now, back to the VolarCreateFile. It returns Flint file with:

    • Flint directives from generated virtual text
    • adjustReportRange, __volarServices.runVisitors, and __volarServices.getDiagnostics (more on them later)
  • Now, back to the typescriptLanguage.createFile. It returns file created by VolarCreateFile (with Symbol.dispose to cleanup opened TS file)

At this point we have initialized Flint file.

Now, Flint starts actual linting:

  • It calls typescriptLanguage.runFileVisitors

  • typescriptLanguage.runFileVisitors tries to access file.__volarServices. If they're present, it hands off visiting to the __volarServices.runVisitors, otherwise it defaults to common ts.SourceFile visiting.

  • __volarServices.runVisitors starts visiting ts.SourceFile. Remember that this is generated virtual text ("service script"). In order to avoid doing extra work it visits only statements that contain at least one "mapping" inside them.

  • When some visitor decides to report an error, it calls ruleRuntime.report

  • Rule runtime tries to adjust all reported ranges with file.adjustReportRange (if present)

  • Now a little hack: we want to have an ability to do both:

    1. Allow @flint.fyi/ts rules seamlessly report errors on generated virtual text
    2. Allow @flint.fyi/vue rules to report errors on source text

    Right now we have only one reporting channel: ruleRuntime.report({ message, range }).

    So, for case 1., rules just call context.report. However, for case 2., rules call reportSourceCode(context, { message, range }). reportSourceCode is exported from @flint.fyi/volar-language.

    reportSourceCode negates the beginning of the range, so that later adjustReportRange can distinguish between case 1. and 2.

  • If file.adjustReportRange detects that the range is reported on source text, it returns it as-is. Otherwise it uses Volar "mappings" to translate range from "service script" positions to "source script".

That's it!!!

I know that there are a lot of questionable hacks, however given #1253 (comment), we would have to refactor a large part of @flint.fyi/core. We can design this refactor these hacks in mind.

I think it's better to merge these hacks, because I'm tired of re-syncing Vue support with @flint.fyi/core changes every few weeks (a decent portion of this PR has been living locally on my machine since October xD)


Now this should automagically work!!!

import { defineConfig } from '@flint.fyi/core'
import { ts } from '@flint.fyi/ts'
import { svelte } from '@flint.fyi/svelte'
import { vue } from '@flint.fyi/vue'

export default defineConfig({
  use: [
    {
      files: [ts.files, vue.files, svelte.files],
      rules: [
        ts.rules({ anyReturns: true })
      ],
    },
    {
      files: [vue.files],
      rules: [
        vue.rules({ vForKeys: true })
      ],
    },
  ]
})

@changeset-bot
Copy link

changeset-bot bot commented Feb 14, 2026

🦋 Changeset detected

Latest commit: abeefe6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 26 packages
Name Type
@flint.fyi/core Patch
@flint.fyi/rule-tester Patch
@flint.fyi/typescript-language Patch
@flint.fyi/volar-language Minor
@flint.fyi/astro Patch
@flint.fyi/browser Patch
@flint.fyi/cli Patch
@flint.fyi/json-language Patch
@flint.fyi/json Patch
@flint.fyi/jsx Patch
@flint.fyi/markdown-language Patch
@flint.fyi/md Patch
@flint.fyi/next Patch
@flint.fyi/node Patch
@flint.fyi/nuxt Patch
@flint.fyi/package-json Patch
@flint.fyi/performance Patch
@flint.fyi/plugin-flint Patch
@flint.fyi/react Patch
@flint.fyi/solid Patch
@flint.fyi/spelling Patch
@flint.fyi/text-language Patch
@flint.fyi/ts Patch
@flint.fyi/yaml-language Patch
@flint.fyi/yaml Patch
@flint.fyi/site Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Feb 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
flint Ready Ready Preview, Comment Feb 14, 2026 7:20am

Request Review

auvred and others added 3 commits February 14, 2026 07:16
Added a changeset for the introduction of Volar.js meta-language with patches for several packages.

const ruleRuntime = await rule.setup({
report(ruleReport) {
let range = ruleReport.range;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, this is copy-pasted from core/src/running/runLintRule.ts. We can refactor it later. I just tried to minimize the overall diff.

@lishaduck
Copy link
Member

I'll review the code in a moment (want to rebase the host work first), but initial thoughts:

This is a very complex PR and it's even hard to properly explain it in a linear order because things interact in weird ways here, so I'll try to explain it in several steps.

Obviously I haven't read the code yet to know if you lied in there 😛 but you did a very good job explaining. It made sense as I read and and was a nice and linear. Don't quiz me though (yet).

I know that there are a lot of questionable hacks, however given #1253 (comment), we would have to refactor a large part of @flint.fyi/core. We can design this refactor these hacks in mind.

I think it's better to merge these hacks

Agreed. Haven't looked at it yet but I'm 100% in favor of getting the hacky version in now and rewriting the rest of the codebase to work with #1253 (comment) later.

because I'm tired of re-syncing Vue support with @flint.fyi/core changes every few weeks (a decent portion of this PR has been living locally on my machine since October xD)

I just want to say that's really impressive :)
I know I usually don't have that much patience. Glad it's finally up though!

"@flint.fyi/volar-language": minor
---

feat: introduce Volar.js meta-language
Copy link
Contributor

@michaelfaith michaelfaith Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From #2298 -> #2322

Suggested change
feat: introduce Volar.js meta-language
Introduce Volar.js meta-language.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🚀 Feature: Introduce Volar.js meta-language

3 participants